class Element {
    constructor(type, props, children) {
        this.type = type;
        this.props = props;
        this.children = children;
    }
}
/**
 * return vDom
 * @param {string} type
 * @param {Object} props
 * @param {Array} children
 */
function createElement(type, props, children) {
    return new Element(type, props, children);
}

// createElement就是换了一种创建对象（虚拟dom，也就是真实dom的js对象映射）的写法，毕竟函数体的逻辑就是return new xxx()，xxx构造函数的逻辑也是简单的挂载属性
const vDom = createElement(
    'ul',
    {
        class: 'list',
        style: 'width: 300px; height: 300px; background-color: orange'
    },
    [
        createElement(
            'li',
            {
                class: 'item',
                'data-index': 0
            },
            [
                createElement('p', { class: 'text' }, ['第一个列表项'])
            ]
        ),
        createElement(
            'li',
            {
                class: 'item',
                'data-index': 1
            },
            [
                createElement(
                    'p', 
                    { class: 'text' }, 
                    [
                        createElement(
                            'span', 
                            {
                                class: "title",
                            },
                            [
                                '第2个列表项'
                            ]
                        )
                    ]
                )
            ]
        ),
        createElement(
            'li',
            {
                class: 'item',
                'data-index': 2
            },
            [
                '第3个列表项'
            ]
        ),
    ]
)

function setAttrs(node, prop, value) {
    switch(prop) {
        case 'value':
            if(node.tagName === 'INPUT' || node.tagName === 'TEXTAREA') {
                node.value = value;
            } else {
                node.setAttribute(prop, value);
            }
            break;
        case 'style': 
            node.style.cssText = value;
            break;
        default: 
            node.setAttribute(prop, value);
            break;
    }
}

function render(vDom) {
    const { type, props, children } = vDom,
        el = document.createElement(type);

    for(let key of Object.keys(props)) {
        setAttrs(el, key, props[key]);
    }

    children.forEach((c) => {
        c = c instanceof Element
            ?
            render(c)
            :
            document.createTextNode(c); // // 字符串就不要添加属性了，无需递归，直接创建文本结点
        el.appendChild(c); // 将每个child添加到创建的真实dom el中即可
    })

    return el;
}

// 类似于挂载
function renderDOM(realDom, rootEl) {
    rootEl.appendChild(realDom);
}

const realDom = render(vDom);

renderDOM(realDom, document.querySelector("#root"));


// diff相关

const vDom2 = createElement(
    'ul',
    {
        class: 'list-wrap',
        style: 'width: 300px; height: 300px; background-color: orange'
    },
    [
        createElement(
            'li',
            {
                class: 'item',
                'data-index': 0
            },
            [
                createElement('p', { class: 'title' }, ['特殊列表项'])
            ]
        ),
        createElement(
            'li',
            {
                class: 'item',
                'data-index': 1
            },
            [
                createElement(
                    'p', 
                    { class: 'text' }, 
                    []
                )
            ]
        ),
        createElement(
            'div',
            {
                class: 'item',
                'data-index': 2
            },
            [
                '第3个列表项'
            ]
        ),
    ]
)


let patches = {}, // 总的补丁包
    vnIndex = 0; // 深度遍历结点序号

function domDiff(oldVDom, newVDom) {
    let index = 0;
    vNodeWalk(oldVDom, newVDom, index);
    return patches;
}

function vNodeWalk(oldNode, newNode, index) {
    let currentPatch = []; // 正在构造的补丁数组

    if(!newNode) {
        currentPatch.push({
            type: 'REMOVE',
            index
        })
    } else if(typeof oldNode === 'string' && typeof newNode === 'string') {
        if(newNode !== oldNode) {
            currentPatch.push({
                type: 'TEXT',
                text: newNode
            });
        }
    } else if(oldNode.type === newNode.type) { // element元素，同标签，需要做两件事情：1.给属性打补丁 2.处理children数组
        // 对比属性构造补丁对象
        const attrPatch = attrsWalk(oldNode.props, newNode.props);
        if(Object.keys(attrPatch).length > 0) {
            currentPatch.push({
                type: 'ATTR',
                attrs: attrPatch
            })
        }

        childrenWalk(oldNode.children, newNode.children); // patchChildren
    } else { // 不同标签，结点被替换掉
        currentPatch.push({
            type: 'REPLACE',
            newNode,
        })
    }

    if(currentPatch.length > 0) {
        patches[index] = currentPatch;
    }
}

function attrsWalk(oldAttrs, newAttrs) {
    let attrPatch = {};

    // 给attrPatch添加属性补丁（对齐newAttrs）
    // newAttrs是否修改属性
    for(let key of Object.keys(oldAttrs)) {
        if(newAttrs[key] !== oldAttrs[key]) {
            attrPatch[key] = newAttrs[key];
        }
    }

    // newAttrs是否删除属性
    for(let key of Object.keys(newAttrs)) {
        if(!oldAttrs.hasOwnProperty(key)) {
            attrPatch[key] = newAttrs[key];
        }
    }

    return attrPatch;
}

function childrenWalk(oldChildren, newChildren) {
    oldChildren.map((c, idx) => {
        vNodeWalk(c, newChildren[idx], ++ vnIndex);
    })
}

domDiff(vDom, vDom2); // 构建补丁包

let rnIndex = 0;

function doPatch(rDom, patches) {
    rNodeWalk(rDom, patches);
}

function rNodeWalk(rNode, patches) {
    const rnPatch = patches[rnIndex ++],
        childNodes = rNode.childNodes;

    if(rnPatch) {
        patchAction(rNode, rnPatch);
    }
    
    [...childNodes].map((c) => {
        rNodeWalk(c, patches);
    })

    
}

function patchAction(rNode, rnPatch) {
    rnPatch.map((p) => {
        switch(p.type) {
            case 'ATTR':
                for(let key of Object.keys(p.attrs)) {
                    const value = p.attrs[key];
                    console.log(key, value);
                    if(value) {
                        setAttrs(rNode, key, value);
                    } else {
                        rNode.removeAttribute(key);
                    }
                }
                break;
            case 'TEXT':
                rNode.textContent = p.text;
                break;
            case 'REPLACE':
                const newNode = (p.newNode instanceof Element)
                    ? render(p.newNode)
                    : document.createTextNode(p.newNode);

                rNode.parentNode.replaceChild(newNode, rNode);
                break;
            case 'REMOVE':
                rNode.parentNode.removeChild(rNode);
                break;
            default:
                break;
        }
    });
}

setTimeout(() => {
    doPatch(realDom, patches);
}, 1000)